<?php
declare(strict_types=1);

namespace Dreamcat\ConfigClient\Impl;

use Components\Utils\Funcs\FileSystemHelper;
use Dreamcat\ConfigClient\ConfigCenterListener;
use Dreamcat\ConfigClient\ConfigCenterReadProcessInterface;
use Dreamcat\ConfigClient\ConfigSaverInterface;
use Dreamcat\ConfigClient\Popo\ConfigVersion;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use RuntimeException;

/**
 * apollo配置进程
 * @author vijay
 */
class ConfigCenterReadProcess implements ConfigCenterReadProcessInterface
{
    /** @var ConfigCenterListener 配置中心服务器 */
    private $configServerLintener;
    /** @var ConfigSaverInterface 配置保存器 */
    private $configSaver;
    /** @var string 缓存文件名 */
    private $cacheFile;
    /**
     * @var array 最后一次拉到配置的版本信息，键是应用关键字的序列化信息，值是版本号
     */
    private $cache = [];
    /** @var LoggerInterface 日志记录器 */
    private $logger;
    /** @var int 拉配置失败的重试次数 */
    private $tryCount = 3;

    /**
     * ApolloReadProcess constructor.
     * @param string $cacheFile 读取配置的缓存文件，要确保有读写权限
     */
    public function __construct(string $cacheFile)
    {
        if (!FileSystemHelper::touchFile($cacheFile)) {
            throw new RuntimeException("缓存文件{$cacheFile}无法创建");
        }
        $this->cacheFile = realpath($cacheFile);
        $cache = file_get_contents($this->cacheFile);
        if (strlen($cache)) {
            $this->cache = @unserialize($cache);
        }
    }

    /**
     * @return int 拉配置失败的重试次数
     */
    public function getTryCount(): int
    {
        return $this->tryCount;
    }

    /**
     * @param int $tryCount 拉配置失败的重试次数
     * @return static 对象本身
     */
    public function setTryCount(int $tryCount): ConfigCenterReadProcess
    {
        if ($tryCount >= 0) {
            $this->tryCount = $tryCount;
        }
        return $this;
    }

    /**
     * @return LoggerInterface 日志记录器
     */
    public function getLogger(): LoggerInterface
    {
        if (!$this->logger) {
            $this->logger = new NullLogger();
        }
        return $this->logger;
    }

    /**
     * @param LoggerInterface $logger 日志记录器
     * @return static 对象本身
     */
    public function setLogger(LoggerInterface $logger): ConfigCenterReadProcess
    {
        $this->logger = $logger;
        return $this;
    }

    /**
     * @inheritDoc
     */
    public function setConfigServerLintener(ConfigCenterListener $configCenterListener
    ): ConfigCenterReadProcessInterface {
        $this->configServerLintener = $configCenterListener;
        return $this;
    }

    /**
     * @inheritDoc
     */
    public function setConfigSaver(ConfigSaverInterface $configSaver): ConfigCenterReadProcessInterface
    {
        $this->configSaver = $configSaver;
        return $this;
    }

    /**
     * @inheritDoc
     */
    public function readConfig(): void
    {
        $notifications = $this->buildVersions();
        while (true) {
            $getNotifica = $this->configServerLintener->notifications($notifications);
            if (!$getNotifica) {
                continue;
            }
            foreach ($getNotifica as $changeAppInfo) {
                $tryCnt = 0;
                $changeKey = $this->getAppInfoKey($changeAppInfo);
                $this->logger->info("监控到应用 {$changeKey} 出现配置变化");
                if (!isset($notifications[$changeKey])) {
                    $notifications[$changeKey] = new ConfigVersion();
                    $notifications[$changeKey]->setAppInfo($this->getAppInfoByKey($changeKey));
                }
                do {
                    ++$tryCnt;
                    $result = $this->configServerLintener->getConfig($notifications[$changeKey]);
                } while ($tryCnt < $this->tryCount && !$result->getSuccess());
                if (!$result->getSuccess()) {
                    $this->getLogger()->error("应用 {$changeKey} 配置拉取失败");
                    continue;
                }
                $this->cache[$changeKey] = $result->getReleaseKey();
                $notifications[$changeKey]->setReleaseKey($result->getReleaseKey());
                $this->logger->info("应用 {$changeKey} 的配置拉取成功，准备更新");
                if ($this->configSaver->saveConfig($result)) {
                    $this->logger->info("应用 {$changeKey} 的配置成功更新");
                } else {
                    $this->logger->warning("应用 {$changeKey} 的配置更新失败！");
                }
            }
            $this->configSaver->onSaveAll();
            $this->saveCache();
        }
    }

    /**
     * 获取应用key
     * @param mixed $appInfo 应用信息
     * @return string key字符串
     */
    protected function getAppInfoKey($appInfo): string
    {
        return json_encode($appInfo);
    }

    /**
     * 通过应用key获取应用信息
     * @param string $appKey 应用key
     * @return mixed 应用信息
     */
    protected function getAppInfoByKey(string $appKey)
    {
        return json_decode($appKey, true);
    }

    /**
     * 从缓存构建版本信息
     * @return ConfigVersion[] 键是应用key，值是版本信息
     */
    protected function buildVersions()
    {
        $notifications = [];
        foreach ($this->cache as $key => $val) {
            $version = new ConfigVersion();
            $version->setReleaseKey($val)
                ->setAppInfo($this->getAppInfoByKey($key));
            $notifications[$key] = $version;
        }
        return $notifications;
    }

    /**
     * 将缓存内容存入缓存文件
     * @return void
     */
    protected function saveCache(): void
    {
        file_put_contents($this->cacheFile, serialize($this->cache));
    }
}

# end of file
